# 研究助力资产配置
import numpy as np
import pandas as pd
import datetime
import os
import Core.Config as Config
import Core.Gadget as Gadget
from AssetAllocation.Markowitz import Markowitz
from AssetAllocation.BlackLitterman import BlackLitterman
from Core.UniDataInterface import Common_Load_DailyBar_DataFrame
from AssetAllocation.Common import Calc_Return_Volatility_Correlation, to_diagonal_matrix
from PerformanceMeasure.performance_measure_v2 import PerformanceMeasure_v2
import matplotlib.pyplot as plt


def build_portfolio_and_calc_metrics(database,
                                     symbol_list,
                                     datetime1, datetime2,
                                     weight_list,
                                     instrument_type_list=[],
                                     pf_name="pf"):

    #
    df_price, df_return = load_data(database, symbol_list, instrument_type_list, datetime1, datetime2)

    df_nav = pd.DataFrame.cumprod(df_return + 1)
    df_nav.iloc[0] = 1
    # df_nav["unit_value"] = pd.DataFrame.cumprod(df_returns["index_return"] + 1)

    num_assets = len(symbol_list)
    if len(weight_list) == 0:
        weight_list = num_assets * [1. / num_assets, ]

    #
    df_nav[pf_name] = 0
    for i in range(len(symbol_list)):
        weight = weight_list[i]
        symbol = symbol_list[i]
        df_nav[pf_name] = df_nav[pf_name] + df_nav[symbol] * weight

    #
    df_nav_copy = df_nav.copy()

    #
    df_nav.reset_index(inplace=True, drop=False)
    df_nav["daily_return"] = df_nav[pf_name] / df_nav[pf_name].shift(1) - 1
    df_nav["benchmark_return"] = 0
    #
    # #
    # date1 = df_nav.iloc[0]["date"]
    # date2 = df_nav.iloc[-1]["date"]
    # days = (date2 - date1).days
    #
    # #
    # pf_return = df_nav.iloc[-1][pf_name] - 1
    # pf_annualized_return = pf_return * (365/days)
    # pf_annualized_sd = df_nav["daily_return"].std * np.sqrt(245)

    #
    performance_measure = PerformanceMeasure_v2(df_nav,
                                                date_field="date",
                                                portfolio_field="daily_return",
                                                benchmark_field="benchmark_return",
                                                net_value_field=pf_name,
                                                annualization=None)
    performance_measure.calculate()
    metrics = performance_measure.result

    print(metrics)

    #
    factor_name = pf_name
    #     df_nav_copy.plot(x="date", y=symbol_list + [factor_name], grid=True, title=factor_name)
    df_nav_copy.plot(y=symbol_list + [factor_name], grid=True, title=factor_name)
    # ax1.set_ylabel('Net Unit Value')
    plt.show()




def load_data(database, symbol_list, instrument_type_list, datetime1, datetime2):
    df_price = pd.DataFrame()
    df_return = pd.DataFrame()
    for i in range(len(symbol_list)):
        symbol = symbol_list[i]
        instrument_type = instrument_type_list[i]
        df_price_tmp = database.GetDataFrame("financial_data", instrument_type + "_dailybar",
                                             filter=[("symbol", symbol), ("date", ">=", datetime1),
                                                     ("date", "<=", datetime2)],
                                             sort=[("date", 1)],
                                             projection=["date", "close"])
        #
        df_price_tmp.rename(columns={"close": symbol}, inplace=True)
        df_return_tmp = df_price_tmp[["date"]].copy()

        df_return_tmp[symbol] = df_price_tmp[symbol] / df_price_tmp[symbol].shift(1) - 1

        #
        if df_price.empty:
            df_price = df_price_tmp
        else:
            df_price = pd.merge(df_price, df_price_tmp, how="left", on="date")
        #
        if df_return.empty:
            df_return = df_return_tmp
        else:
            df_return = pd.merge(df_return, df_return_tmp, how="left", on="date")

        # df = Common_Load_DailyBar_DataFrame(database, symbol, datetime1=datetiem1, datetime2=datetiem2, instrument_type=None)

    #
    df_price.set_index("date",inplace=True)
    df_return.set_index("date",inplace=True)
    return df_price, df_return


#
def test(database):

    # 读取资产
    datetime1 = datetime.datetime(2024, 1, 1)
    datetime2 = datetime.datetime(2024, 7, 1)
    symbol_list = ["000300.SH", "000852.SH", "H11001.CSI", "AU.SHF", "SPX.GI"]
    instrument_type_list = ["index", "index", "index", "futureindex", "index"]

    # df_price, df_return = load_data(database, symbol_list, instrument_type_list, datetime1, datetime2)
    #
    # #
    # profiles = Calc_Return_Volatility_Correlation(df_return, annualized_days=245)
    # print(symbol_list)
    # print("Returns")
    # print(profiles["Returns"])
    # print("Volatility")
    # print(profiles["Volatilities"])
    # print("")
    # print("Correlation")
    # print(profiles["Correlation"])
    # print(profiles["Covariance"])

    #
    build_portfolio_and_calc_metrics(database,
                                     symbol_list=symbol_list,
                                     datetime1=datetime1,
                                     datetime2=datetime2,
                                     weight_list=[],
                                     instrument_type_list=instrument_type_list)



def test_build_pf_based_mv(database):
    # 读取资产
    backtest_datetime1 = datetime.datetime(2023, 1, 1)
    backtest_datetime2 = datetime.datetime(2023, 12, 31)
    symbol_list = ["000300.SH", "000852.SH", "H11001.CSI", "AU.SHF", "SPX.GI"]
    instrument_type_list = ["index", "index", "index", "futureindex", "index"]

    df_price, df_return = load_data(database, symbol_list, instrument_type_list, backtest_datetime1, backtest_datetime2)
    profiles = Calc_Return_Volatility_Correlation(df_return, annualized_days=245)

    #
    annual_returns = profiles["Returns"]
    annual_std_dev = profiles["Volatilities"]
    correlation = profiles["Correlation"]

    print(symbol_list)
    print(annual_returns)
    print(annual_std_dev)
    print(correlation)

    # 用df_Return 初始化
    mv = Markowitz(annual_returns, annual_std_dev, correlation, )
    optimal_weights = mv.optimize(optimize_objective="MAXSHARPE")
    print(optimal_weights)

    mv.plot_efficient_frontier()

    #
    datetime1 = datetime.datetime(2024, 1, 1)
    datetime2 = datetime.datetime(2024, 7, 1)
    build_portfolio_and_calc_metrics(database,
                                     symbol_list=symbol_list,
                                     datetime1=datetime1,
                                     datetime2=datetime2,
                                     weight_list=optimal_weights,
                                     instrument_type_list=instrument_type_list)


def test_build_pf_based_BL(database):
    # 读取资产
    backtest_datetime1 = datetime.datetime(2023, 1, 1)
    backtest_datetime2 = datetime.datetime(2023, 12, 31)
    symbol_list = ["000300.SH", "000852.SH", "H11001.CSI", "AU.SHF", "SPX.GI"]
    instrument_type_list = ["index", "index", "index", "futureindex", "index"]

    df_price, df_return = load_data(database, symbol_list, instrument_type_list, backtest_datetime1, backtest_datetime2)
    profiles = Calc_Return_Volatility_Correlation(df_return, annualized_days=245)

    #
    hist_annual_returns = profiles["Returns"]
    hist_annual_std_dev = profiles["Volatilities"]
    hist_correlation = profiles["Correlation"]

    print(symbol_list)
    print(hist_annual_returns)
    print(hist_annual_std_dev)
    print(hist_correlation)

    # 用df_Return 初始化
    mv = Markowitz(hist_annual_returns, hist_annual_std_dev, hist_correlation, )
    optimal_weights = mv.optimize(optimize_objective="MAXSHARPE")
    print("Weight 基于MV")
    print(optimal_weights)


    # 拍脑袋组合
    datetime1 = datetime.datetime(2024, 1, 1)
    datetime2 = datetime.datetime(2024, 7, 1)
    build_portfolio_and_calc_metrics(database,
                                     pf_name="pai-nao-dai",
                                     symbol_list=symbol_list,
                                     datetime1=datetime1,
                                     datetime2=datetime2,
                                     weight_list=[0.02, 0.02, 0.90, 0.03, 0.03],
                                     instrument_type_list=instrument_type_list)

    # 基于历史数据，拍脑袋参数
    prior_returns = [0.01, 0.01, 0.05, 0.10, 0.20]
    prior_volatility = [0.13, 0.15, 0.01, 0.10, 0.13]

    # 市场预期
    view_weight_P = [[1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1]]
    view_Q = [-0.05, -0.03, 0.05, 0.16, 0.10] # 低配股票（小盘好于大盘）
    view_var = [0.13, 0.14, 0.001, 0.09, 0.14]

    # 用方差构建协方差矩阵
    for i in range(len(view_var)):
        view_var[i] = view_var[i] * view_var[i]
    view_covariance_matrix = to_diagonal_matrix(view_var)

    #
    bl = BlackLitterman(prior_returns=prior_returns,
                        prior_volatility=prior_volatility,
                        correlation_matrix=hist_correlation,
                        view_weight_matrix_P=view_weight_P,
                        view_list_Q=view_Q,
                        view_variance_omega=view_covariance_matrix,
                        lambda_market=1,
                        lambda_user=6.5,
                        tau=1,
                        )
    #
    # weights = bl.optimize()
    # print("BL WEIGHT")
    # print(weights)

    mv = Markowitz(expected_return_array=bl.bl_expected_return, covariance_matrix=bl.bl_sigma)
    weights = mv.optimize(optimize_objective="MAXSHARPE")
    print("MV WEIGHT 基于后验收益和方差")
    print(weights)



    #
    datetime1 = datetime.datetime(2024, 1, 1)
    datetime2 = datetime.datetime(2024, 7, 1)
    build_portfolio_and_calc_metrics(database,
                                     symbol_list=symbol_list,
                                     datetime1=datetime1,
                                     datetime2=datetime2,
                                     weight_list=optimal_weights,
                                     instrument_type_list=instrument_type_list)

#
if __name__ == '__main__':

    path_filename = os.getcwd() + "\..\Config\config_local.json"
    database = Config.create_database(database_type="MySQL", config_file=path_filename, config_field="MySQL")

    # test(database)
    # test_build_pf_based_mv(database)
    test_build_pf_based_BL(database)